package com.csust.automaticteach.app.teacher.iflytek.ppt;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.csust.automaticteach.app.teacher.iflytek.ppt.model.vo.ProgressResponse;
import com.csust.automaticteach.exception.BusinessException;
import com.csust.automaticteach.exception.ErrorCode;
import com.csust.automaticteach.model.dto.generateppt.CreateByOutlineRequest;
import com.csust.automaticteach.model.dto.generateppt.GeneratePPTRequest;
import com.csust.automaticteach.model.dto.generateppt.GetOutlineRequest;
import com.csust.automaticteach.model.dto.generateppt.ThemeTemplateQueryRequest;
import com.csust.automaticteach.model.vo.generateppt.response.CheckProgressVO;
import com.csust.automaticteach.utils.GenericEntityConverter;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;


@Component
@Slf4j
@Lazy
public class ApiClient {

    @Resource
    private Authentication authentication;
    @Resource
    private GeneratePPTConfig generatePPTConfig;
    /**
     * 响应式编程客户端
     */
    private final WebClient webClient;

    /**
     * 媒体类型
     */
    private static final String MEDIA_TYPE_JSON = "application/json;charset=utf-8";
    /**
     * 创建接口的url
     */
    private final static String baseUrl;

    // 初始化baseUrl
    static {
        baseUrl = "https://zwapi.xfyun.cn/api/ppt/v2";
    }

    public ApiClient(WebClient.Builder webClientBuilder) {
        //模拟测试
        this.webClient = webClientBuilder.baseUrl(baseUrl).build();
    }

    /**
     * 获取模板列表
     * PPT主题列表查询
     * @return 模板列表
     */
    public String getTemplateList(ThemeTemplateQueryRequest templateQueryRequest) {
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);
        JSONObject json = GenericEntityConverter.convertToJson(templateQueryRequest);

        validateParameters(appId, ts, signature);

        return executePost(
                baseUrl + "/template/list",
                appId, ts, signature,
                json.toString()
        );
    }

    /**
     * 通过 query 创建ppt大纲
     *
     * @param appId     应用ID
     * @param timestamp 时间戳
     * @param signature 签名
     * @param query     查询内容
     * @return 创建结果
     */
    public String createOutline(String appId, String timestamp, String signature, String query) {
        validateParameters(appId, timestamp, signature, query);

        Map<String, Object> params = new HashMap<>();
        params.put("query", query);

        return executePostForm(
                baseUrl + "/createOutline",
                appId, timestamp, signature,
                params
        );
    }
    /**
     * 通过 query 创建ppt大纲
     * @param query     查询内容
     * @return ppt大纲
     * @throws IOException 文件操作异常
     */
    public String createOutlineByQuery(GeneratePPTRequest query) throws IOException {
        //创建鉴权参数
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);
        //参数校验
        validateParameters(appId, ts, signature,query.getQuery());
        //创建请求参数
        Map<String, Object> params = GenericEntityConverter.entityToMap(query);
        //发送请求并得到执行结果
        return executePostForm(
                baseUrl + "/createOutline",
                appId, ts, signature,
                params
        );
    }

    //原方法
    public String createOutlineByDoc(String appId, String timestamp, String signature, File file) {
        validateParameters(appId, timestamp, signature);

        Map<String, Object> params = new HashMap<>();
        params.put("file", file);
        params.put("fileName", file.getName());

        return executeMultipartPost(
                baseUrl + "/createOutlineByDoc",
                appId, timestamp, signature,
                params
        );
    }

    /**
     * 自定义大纲生成
     * 基于用户提示、文档等相关内容生成PPT大纲。
     * query参数不得超过12000字，上传文件支持pdf(不支持扫描件)、doc、docx、txt、md格式的文件，注意：txt格式限制100万字以内，其他文件限制10M。
     *
     * @param getOutlineRequest 文件
     * @return 大纲
     * @throws IOException 文件操作异常
     */
    public String createOutlineByDoc(GetOutlineRequest getOutlineRequest) throws IOException {
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);
        validateParameters(appId, ts, signature);
        //将实体类转化为 map
        Map<String, Object> params = GenericEntityConverter.entityToMap(getOutlineRequest);
        return executeMultipartPost(
                baseUrl + "/createOutlineByDoc",
                appId, ts, signature,
                params
        );
    }

    /**
     * PPT生成（直接根据用户输入要求，获得最终PPT）
     * 基于用户提示、文档等相关内容生成PPT，字数不得超过12000字，文件限制10M。
     * 用户生成PPT要求（最多12000字；query、file、fileUrl必填其一）
     * 注意：query不能为空字符串、仅包含空格的字符串
     *
     * @param appId     应用ID
     * @param timestamp 时间戳
     * @param signature 签名
     * @param file      文件
     * @return 创建结果
     */
    public String create(String appId, String timestamp, String signature, File file){
        validateParameters(appId, timestamp, signature);

        Map<String, Object> params = new HashMap<>();
        params.put("file", file);
        params.put("fileName", file.getName());

        return executeMultipartPost(
                baseUrl + "/create",
                appId, timestamp, signature,
                params
        );
    }

    //原方法
    public String createPptByOutline(String appId, String ts, String signature, String outline){
        validateParameters(appId, ts, signature);

        JSONObject json = new JSONObject();
        json.set("outline", JSONUtil.parseObj(outline));
        json.set("query", "test");

        return executePost(
                baseUrl + "/createPptByOutline",
                appId, ts, signature,
                json.toString()
        );
    }

    /**
     * 通过大纲生成PPT
     *
     * @param createByOutlineRequest 生成参数请求
     * @return 创建结果
     * @throws IOException 创建异常
     */
    public String createPptByOutline(CreateByOutlineRequest createByOutlineRequest) throws IOException {
        //1.获取相关校验参数
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);

        validateParameters(appId, ts, signature);

        JSONObject json = GenericEntityConverter.convertToJson(createByOutlineRequest);

        return executePost(
                baseUrl + "/createPptByOutline",
                appId, ts, signature,
                json.toString()
        );
    }


    //原方法
    public String checkProgress(String appId, String timestamp, String signature, String sid) {
        validateParameters(appId, timestamp, signature, sid);

        Map<String, Object> params = new HashMap<>();
        params.put("sid", sid);

        return executeGet(
                baseUrl + "/progress",
                appId, timestamp, signature,
                params
        );
    }
    /**
     * PPT进度查询
     * 注：该接口设置限流，三秒访问一次
     * @param sid       请求大纲唯一id，后续通过大纲生成ppt可能需要
     * @return 进度查询结果
     * @throws IOException If an I/O error occurs during the request.
     */
    public String checkProgress(String sid) throws IOException {
        //1.获取相关校验参数
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);
        //2.校验参数
        validateParameters(appId, ts, signature, sid);

        Map<String, Object> params = new HashMap<>();
        params.put("sid", sid);

        return executeGet(
                baseUrl + "/progress",
                appId, ts, signature,
                params
        );
    }

    /**
     * 执行POST请求
     *
     * @param url       请求的URL
     * @param appId     应用ID
     * @param timestamp 时间戳
     * @param signature 签名
     * @param body      请求体
     * @return 响应内容json字符串
     */
    private String executePost(String url, String appId, String timestamp,
                               String signature, String body) {
        HttpResponse response = HttpRequest.post(url)
                .header("appId", appId)
                .header("timestamp", timestamp)
                .header("signature", signature)
                .header("Content-Type", ApiClient.MEDIA_TYPE_JSON)
                .body(body)
                .timeout(60 * 10 * 1000)
                .execute();

        return handleResponse(response);
    }

    /**
     * Execute a POST request with form data.
     *
     * @param url       The URL to send the request to.
     * @param appId     The appId.
     * @param timestamp The timestamp.
     * @param signature The signature.
     * @param params    The form data.
     * @return The response body.
     */

    private String executePostForm(String url, String appId, String timestamp,
                                   String signature, Map<String, Object> params) {
        HttpResponse response = HttpRequest.post(url)
                .header("appId", appId)
                .header("timestamp", timestamp)
                .header("signature", signature)
                .form(params)
                .timeout(60 * 10 * 1000)
                .execute();

        return handleResponse(response);
    }

    /**
     * @param url       The URL to send the request to.
     * @param appId     The appId.
     * @param timestamp The timestamp.
     * @param signature The signature.
     * @param params    The form data.
     * @return The response body.
     */
    private String executeMultipartPost(String url, String appId, String timestamp,
                                        String signature, Map<String, Object> params) {
        HttpResponse response = HttpRequest.post(url)
                .header("appId", appId)
                .header("timestamp", timestamp)
                .header("signature", signature)
                .form(params)
                .timeout(60 * 10 * 1000)
                .execute();

        return handleResponse(response);
    }

    /**
     * @param url       The URL to send the request to.
     * @param appId     The appId.
     * @param timestamp The timestamp.
     * @param signature The signature.
     * @param params    The form data.
     * @return The response body.
     */
    private String executeGet(String url, String appId, String timestamp,
                              String signature, Map<String, Object> params) {
        HttpResponse response = HttpRequest.get(url)
                .header("appId", appId)
                .header("timestamp", timestamp)
                .header("signature", signature)
                .form(params)
                .timeout(60 * 10 * 1000)
                .execute();

        return handleResponse(response);
    }

    /**
     * @param sid       the sid to request
     * @return The response body.
     * @throws IOException if an I/O error occurs.
     */
    public Mono<ProgressResponse> GenerateProgressAsync(String sid) throws IOException {
        //1.获取相关校验参数
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);
        //2.校验参数
        validateParameters(appId, ts, signature);
        return webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/progress")
                        .queryParam("sid", sid)
                        .build())
                .header("appId", appId)
                .header("timestamp", ts)
                .header("signature", signature)
                .retrieve()
                .bodyToMono(ProgressResponse.class)
                .flatMap(response -> {
                    log.info("response:{}", response);
                    if ((response != null  && ("done".equals(response.getData().getPptStatus())))) {
                        return Mono.just(response); // 完成时返回 progressResponse .getData().getPptUrl()
                    } else {
                        log.info(" error response:{}", response);
                        return Mono.error(new NotReadyException()); // 触发重试
                    }
                })
                .timeout(Duration.ofMinutes(10))
                .retryWhen(Retry.fixedDelay(30, Duration.ofSeconds(5)))
                ;// 重试策略

    }
    // 自定义异常控制重试
    private static class NotReadyException extends RuntimeException {
        public NotReadyException() {
            super("PPT not ready yet");
        }
    }

    /**
     * @param sid the sid to request
     * @return The response body.
     * @throws IOException if an I/O error occurs.
     */
    public Mono<CheckProgressVO> GenerateProgressImmediately(String sid) throws IOException {
        //1.获取相关校验参数
        String appId = generatePPTConfig.getAPPID();
        long timestamp = System.currentTimeMillis() / 1000;
        String ts = String.valueOf(timestamp);
        String signature = authentication.getSignature(appId, generatePPTConfig.getAPISecret(), timestamp);
        //2.校验参数
        validateParameters(appId, ts, signature);
        return webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/progress")
                        .queryParam("sid", sid)
                        .build())
                .header("appId", appId)
                .header("timestamp", ts)
                .header("signature", signature)
                .retrieve()
                .bodyToMono(ProgressResponse.class)
                .flatMap(response -> {
                    log.info("response:{}", response);
                    //构造返回对象 checkProgressVO
                    Integer donePages = response.getData().getDonePages();//已完成的页数
                    Integer totalPages = response.getData().getTotalPages();//总页数
                    CheckProgressVO checkProgressVO = new CheckProgressVO();
                    Double finishRatio = donePages * 1.0 / totalPages;//完成比例
                    checkProgressVO.setFinishRatio(finishRatio);
                    String pptStatus = response.getData().getPptStatus();//ppt状态
                    checkProgressVO.setStatus(pptStatus);
                    if ((response != null && ("done".equals(pptStatus)))) {
                        String pptUrl = response.getData().getPptUrl();
                        checkProgressVO.setPptUrl(pptUrl);
                        return Mono.just(checkProgressVO); // 完成时返回 checkProgressVO
                    } else {
                        log.info("PPT is not ready yet. Current progress: {}", response);
                        return Mono.error(new NotReadyImmediateException(checkProgressVO)); // 触发重试
                    }
                })
                .timeout(Duration.ofMinutes(10))
                .retryWhen(Retry.fixedDelay(30, Duration.ofSeconds(5)))
                ;// 重试策略

    }

    // 自定义异常控制重试
    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class NotReadyImmediateException extends RuntimeException {
        public NotReadyImmediateException(CheckProgressVO checkProgressVO) {
            super("PPT not ready yet");
            this.checkProgressVO = checkProgressVO;
        }

        private final CheckProgressVO checkProgressVO;
    }


    /**
     * Execute a GET request with form parameters.
     *
     * @param response the response
     * @return the response body
     */
    private String handleResponse(HttpResponse response) {
        if (!response.isOk()) {
            String body = response.body();
            try {
                throw new IOException("Unexpected code: " + response.getStatus() + ", Body: " + body);
            } catch (IOException e) {
                log.error("fail to generate ppt{}", e.getMessage());
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "ppt生成失败");
            }
        }
        return response.body();
    }

    /**
     * 验证参数
     *
     * @param params 参数
     */

    private void validateParameters(String... params) {
        for (String param : params) {
            if (param == null || param.isEmpty()) {
                throw new IllegalArgumentException("Parameter cannot be null or empty");
            }
        }
    }
}